###############################################################################
# SuperLinks                                                                  #
# Copyright (c) 2000-2007 Greg Nolle (http://greg.nolle.co.uk)                #
###############################################################################
# This program is free software; you can redistribute it and/or modify it     #
# under the terms of the GNU General Public License as published by the Free  #
# Software Foundation; either version 2 of the License, or (at your option)   #
# any later version.                                                          #
#                                                                             #
# This program is distributed in the hope that it will be useful, but WITHOUT #
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or       #
# FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for   #
# more details.                                                               ##                                                                             #
# You should have received a copy of the GNU General Public License along     #
# with this program; if not, write to the Free Software Foundation, Inc.,     #
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.                 #
###############################################################################
# ControlPanel/Databases/SL1.pm.pl -> SL1 database conversion module          #
###############################################################################
# DON'T EDIT BELOW UNLESS YOU KNOW WHAT YOU'RE DOING!                         #
###############################################################################
package CP::Database;

BEGIN { require "System.pm.pl"; import System qw($SYSTEM); }
BEGIN { require "General.pm.pl"; import General qw($GENERAL); }
require "Standard.pm.pl";

use HTML::Dialog;
use Archive::Tar;
use URI;
use File::Path;
use File::Copy;
use Cwd;
use strict;

sub new {
  my $class = shift;
	my $self = {
	  "DB"    => undef,
    "NAME"  => "SuperLinks Version 1.x",
    @_
  };
  return bless ($self, $class);
}

sub DESTROY { }

###############################################################################
# PrintFields subroutine
sub PrintFields {
  my $self = shift;
  my ($Error) = @_;

  my $Dialog = HTML::Dialog->new();

  $Error = "<p><font class=\"error\">There were errors:<ul>$Error</ul>" if ($Error);

  my $Return = $Dialog->TableRow(items => [{ body => "Database: <b>$self->{'NAME'}</b><br>Please fill out all the required (<font class=\"form-required\">*</font>) fields:".$Error, colspan => 2 }]);
  
  $Return .= $Dialog->FormTextBox(
    name      => "FORM_BACKUP",
    subject   => "Path to SuperLinks 1.x backup",
    required  => 1
  );

  $Return .= $Dialog->FormCheckBox(
    name      => "FORM_COMPRESS",
    items     => [{ value => "1", label => "Pack the new backup into a .tar or .tar.gz archive?" }]
  );
  
  $Return .= $Dialog->FormButton(
    items     => [
      { type => "submit", value => "Convert" },
      { type => "reset", value => "Cancel" }
    ], join => "&nbsp;"
  );
  
  return $Return;
}

###############################################################################
# CheckFields subroutine
sub CheckFields {
  my $self = shift;
  
  my $Error;
  
  $Error  = "<li>You didn't fill in the \"Path to SuperLinks 1.x backup\" field." if ($SL::QUERY{'FORM_BACKUP'} eq "");
  $Error .= "<li>The value you entered for the \"Path to SuperLinks 2.x backup\" field is invalid." unless ($SL::QUERY{'FORM_BACKUP'} eq "" || (-e $SL::QUERY{'FORM_BACKUP'} && (-d $SL::QUERY{'FORM_BACKUP'} || $SL::QUERY{'FORM_BACKUP'} =~ /\.tar(\.gz)?$/i)));
  
  return $Error;
}

###############################################################################
# Main subroutine
sub Main {
  my $self = shift;
  my ($Log, $Exit) = @_;

  $Log->Line("", "uplevel");
  $Log->Line("Beginning conversion of $self->{'NAME'} database.\n\n", "element");

  my $BackupDirectory;
  
  if ($SL::QUERY{'FORM_BACKUP'} =~ /\.tar(\.gz)?$/i) {
    $Log->Line("Unpacking old backup... ", "element");

    my $tar = Archive::Tar->new();
       $tar->read($SL::QUERY{'FORM_BACKUP'}, ($1 eq ".gz" ? 1 : 0));

    my $cwd = cwd();

    rmtree("$SYSTEM->{'TEMP_PATH'}/Convert");
    mkdir("$SYSTEM->{'TEMP_PATH'}/Convert", 0777) || ($Log->Line("Error: Cannot create temp directory. $!", "error") and &$Exit);
    chmod(0777, "$SYSTEM->{'TEMP_PATH'}/Convert");
    
    my $cwd = cwd();
    chdir("$SYSTEM->{'TEMP_PATH'}/Convert");
    my @files = $tar->list_files();
    $tar->extract(@files);
    chdir($cwd);

    my @file = split(/\//, $SL::QUERY{'FORM_BACKUP'});
    my $file = $file[$#file];
       $file =~ s/\.tar(\.gz)?$//i;
    $BackupDirectory = "$SYSTEM->{'TEMP_PATH'}/Convert/$file";
    
    $Log->Line("Done.", "success");
  } else {
    $Log->Line("Checking old backup directory... ", "element");
    unless (-d $SL::QUERY{'FORM_BACKUP'}) {
      $Log->Line("Error: Specified backup does not appear to be a .tar/.tar.gz file or a directory", "error");
      &$Exit;
    }
    $BackupDirectory = $SL::QUERY{'FORM_BACKUP'};
    $Log->Line("Done.", "success");
  }
  
  $Log->Line("Creating new backup directory... ", "element");
  my $NewBackup = "SL-".time();
  mkdir("$SYSTEM->{'BACKUP_PATH'}/$NewBackup", 0777) || ($Log->Line("Error: $!", "error") and &$Exit);
  chmod(0777, "$SYSTEM->{'BACKUP_PATH'}/$NewBackup");
  $Log->Line("Done.", "success");
  
  $Log->Line("Converting tables... ", "element");
  $Log->Line("", "uplevel");
  
  $Log->Line("Converting \"Categories\" table...", "element");
  $Log->Line("", "uplevel");
  $Log->Line("Getting old data... ", "element");
  my @Data = $self->myGetTable("Categories", $BackupDirectory);
  ($Log->Line("Error: $self->{'ERROR'}", "error") and &$Exit) if ($self->{'ERROR'});
  $Log->Line("Done.", "success");
  $Log->Line("Processing data... ", "element");
  foreach my $record (@Data) {
    $record->{'TITLE'}      = $record->{'NAME'};
    $record->{'FULL_TITLE'} = $record->{'FULL_PATH'};
    $record->{'ROOT'}       = "0";
    if ($record->{'ID_PATH'}) {
      $record->{'ROOT'} .= ",".$record->{'ID_PATH'};
    }
    $record->{'USER_ADD'}   = "1";
  }
  $Log->Line("Done.", "success");
  $Log->Line("Saving new data... ", "element");
  $self->mySaveTable($NewBackup, "Categories", \@Data) || ($Log->Line("Error: $self->{'ERROR'}", "error") and &$Exit);
  $Log->Line("Done.", "success");
  $Log->Line("", "downlevel");
  
  $Log->Line("Converting \"Rates\" table...", "element");
  $Log->Line("", "uplevel");
  $Log->Line("Getting old data... ", "element");
  my @Data = $self->myGetTable("Rates", $BackupDirectory);
  ($Log->Line("Error: $self->{'ERROR'}", "error") and &$Exit) if ($self->{'ERROR'});
  $Log->Line("Done.", "success");
  $Log->Line("Processing data... ", "element");
  my %Rates;
  foreach my $record (@Data) {
    $record->{'RID'} = $record->{'LID'};
    $Rates{ $record->{'RID'} }->{'TOTAL'} += $record->{'RATE'};
    $Rates{ $record->{'RID'} }->{'COUNT'}++;
  }
  $Log->Line("Done.", "success");
  $Log->Line("Saving new data... ", "element");
  $self->mySaveTable($NewBackup, "Rates", \@Data) || ($Log->Line("Error: $self->{'ERROR'}", "error") and &$Exit);
  $Log->Line("Done.", "success");
  $Log->Line("", "downlevel");
  
  $Log->Line("Converting \"Reviews\" table...", "element");
  $Log->Line("", "uplevel");
  $Log->Line("Getting old data... ", "element");
  my @Data = $self->myGetTable("Reviews", $BackupDirectory);
  ($Log->Line("Error: $self->{'ERROR'}", "error") and &$Exit) if ($self->{'ERROR'});
  $Log->Line("Done.", "success");
  $Log->Line("Processing data... ", "element");
  my %Reviews;
  foreach my $record (@Data) {
    $record->{'RID'}           = $record->{'LID'};
    $record->{'NAME'}          = "Unknown reviewer";
    $record->{'CREATE_SECOND'} = time;
    $record->{'CREATE_DATE'}   = &Standard::ConvertEpochToDate($record->{'CREATE_SECOND'});
    $record->{'CREATE_TIME'}   = &Standard::ConvertEpochToTime($record->{'CREATE_SECOND'});
    $Reviews{ $record->{'RID'} }++;
  }
  $Log->Line("Done.", "success");
  $Log->Line("Saving new data... ", "element");
  $self->mySaveTable($NewBackup, "Reviews", \@Data) || ($Log->Line("Error: $self->{'ERROR'}", "error") and &$Exit);
  $Log->Line("Done.", "success");
  $Log->Line("", "downlevel");

  $Log->Line("Converting \"Resources\" table...", "element");
  $Log->Line("", "uplevel");
  $Log->Line("Getting old data... ", "element");
  my @Data = $self->myGetTable("Resources", $BackupDirectory);
  ($Log->Line("Error: $self->{'ERROR'}", "error") and &$Exit) if ($self->{'ERROR'});
  $Log->Line("Done.", "success");
  $Log->Line("Processing data... ", "element");
  foreach my $record (@Data) {
    my $URI = URI->new($record->{'URL'});
    my @Segments = $URI->path_segments();
    my $file = pop(@Segments);
    my ($filename, $extension) = ($file =~ m/^(.*)\.(.*?)$/);
    
    if ($Rates{ $record->{'ID'} }->{'COUNT'} >= 1) {
      $record->{'RATING'} = &Standard::Round($Rates{ $record->{'ID'} }->{'TOTAL'} / $Rates{ $record->{'ID'} }->{'COUNT'});
    } else {
      $record->{'RATING'} = 0;
    }
    $record->{'REVIEWS'}       = $Reviews{ $record->{'ID'} };
    $record->{'SHOW_EMAIL'}    = "1";
    $record->{'STATUS'}        = $record->{'APPROVED'};
    $record->{'URL_PROTOCOL'}  = $URI->scheme();
    $record->{'URL_HOST'}      = $URI->host();
    $record->{'URL_PORT'}      = $URI->port();
    $record->{'URL_DIRECTORY'} = join("/", @Segments);
    $record->{'URL_FILENAME'}  = $filename;
    $record->{'URL_EXTENSION'} = $extension;
    $record->{'URL_QUERY'}     = $URI->query();
  }
  $Log->Line("Done.", "success");
  $Log->Line("Saving new data... ", "element");
  $self->mySaveTable($NewBackup, "Resources", \@Data) || ($Log->Line("Error: $self->{'ERROR'}", "error") and &$Exit);
  $Log->Line("Done.", "success");
  $Log->Line("", "downlevel");

  $Log->Line("", "downlevel");
  
  if ($SL::QUERY{'FORM_BACKUP'} =~ /\.tar(\.gz)?$/i) {
    $Log->Line("Removing temporary directory... ", "element");
    if (rmtree($BackupDirectory)) {
      $Log->Line("Done.", "success");
    } else {
      $Log->Line("Error: $!", "error");
    }
  }
  
  if ($SL::QUERY{'FORM_COMPRESS'}) {
    $Log->Line("Packing the backup... ", "element");
    my $result = $self->myPack($NewBackup);
    if ($result == 1) {
      $Log->Line("Done.", "success");
      $Log->Line("Removing backup directory... ", "element");
      if (rmtree("$SYSTEM->{'BACKUP_PATH'}/$NewBackup")) {
        $Log->Line("Done.", "success");
      } else {
        $Log->Line("Error: $!", "error");
      }
    } else {
      $Log->Line("Error: $result", "error");
    }
  }
  
  $Log->Line("Writing backup description file... ", "element");
  if (open(DESC, ">$SYSTEM->{'BACKUP_PATH'}/$NewBackup.txt")) {
    print DESC "Converted database from $self->{'NAME'}.";
    close(DESC);
    $Log->Line("Done.", "success");
  } else {
    $Log->Line("Error: $!", "error");
  }

  $Log->Line("Writing backup log file... ", "element");
  if (open(LOG, ">$SYSTEM->{'BACKUP_PATH'}/$NewBackup.log")) {
    print LOG $Log->{'LOG'};
    close(LOG);
    $Log->Line("Done.", "success");
  } else {
    $Log->Line("Error: $!", "error");
  }
  
  $Log->Line("\n\n", "");
  $Log->Line("All done!", "element");
  $Log->Line("", "downlevel");

  return 1;
}

###############################################################################
# myGetTable subroutine
sub myGetTable {
  my $self = shift;
  my ($Table, $Dir) = @_;

  open(EXPORT, "$Dir/$Table/$Table.export") || ($self->{'ERROR'} = $! and return);
  my @EXPORT = <EXPORT>;
  close(EXPORT);
  
  $self->myClearCfg($Table);
  $self->myLoadCfg($Table);
  
  my @Data;
  if (scalar(@EXPORT) > 2) {
    for (my $h = 1; $h <= $#EXPORT - 1; $h++) {
      my ($key, $data) = split(/\|\~\|/, $EXPORT[$h]);
      my $record = $self->{'DB'}->myDecodeData($Table, $data);
      push(@Data, $record);
    }
  }
  
  $self->myClearCfg($Table); 

  return @Data;  
}

###############################################################################
# mySaveTable subroutine
sub mySaveTable {
  my $self = shift;
  my ($Backup, $Table, $Data) = @_;

  mkdir("$SYSTEM->{'BACKUP_PATH'}/$Backup/$Table", 0777) || ($self->{'ERROR'} = $! and return);
  chmod(0777, "$SYSTEM->{'BACKUP_PATH'}/$Backup/$Table");
  
  do "$SYSTEM->{'DB_PATH'}/$Table.cfg";
  
  open(EXPORT, ">$SYSTEM->{'BACKUP_PATH'}/$Backup/$Table/$Table.export") || ($self->{'ERROR'} = $! and return);
  print EXPORT join("|", @{$DB::Columns{$Table}})."\n";
  foreach my $record (@{ $Data }) {
    print EXPORT $record->{ $DB::PrimaryKey{$Table} }."|~|".$self->{'DB'}->myEncodeData($Table, $record)."\n";
  }
  print EXPORT "#".scalar(@{ $Data });
  close(EXPORT);

  copy("$SYSTEM->{'DB_PATH'}/$Table.cfg", "$SYSTEM->{'BACKUP_PATH'}/$Backup/$Table.cfg") || ($self->{'ERROR'} = $! and return);
  
  return 1;
}

###############################################################################
# myPack subroutine
sub myPack {
  my $self = shift;
  my ($Backup) = @_;

  my $Cwd = cwd();
  chdir($SYSTEM->{'BACKUP_PATH'});

  my @Files = $self->myExplore($SYSTEM->{'BACKUP_PATH'}, $Backup);

  my $Tar = Archive::Tar->new();
     $Tar->add_files(@Files) || (chdir($Cwd) and return $Archive::Tar::error);

  if ($Archive::Tar::compression) {
    $Tar->write("$Backup.tar.gz", 1);
  } else {
    $Tar->write("$Backup.tar");
  }

  chdir($Cwd);

  return 1;
}

###############################################################################
# myExplore subroutine
sub myExplore {
  my $self = shift;
  my ($Base, $Directory) = @_;

  my @Return;

  opendir(DIR, $Base."/".$Directory);
  my @List = readdir(DIR);
  closedir(DIR);

  foreach my $file (@List) {
    next if ($file =~ /^\.\.?$/);
    
    if (-d $Base."/".$Directory."/".$file) {
      push(@Return, $self->myExplore($Base, $Directory."/".$file));
    } else {
      push(@Return, $Directory."/".$file);
    }
  }
  
  return @Return;
}

###############################################################################
# myLoadCfg subroutine
sub myLoadCfg {
  my $self = shift;
  my ($Table) = @_;

  $DB::Path{$Table} = "$SYSTEM->{'DB_PATH'}/$Table";

  if ($Table eq "Resources") {
    $DB::Table{$Table} = {
      ID                  => [ 0, "autonumber",    16, 1,           ""],
      TITLE               => [ 1,     "string",   128, 1,           ""],
      URL                 => [ 3,     "string",   256, 1,           ""],
      CATEGORY            => [ 4,     "number",    16, 1,           ""],
      DESCRIPTION         => [ 5,     "string",   512, 1,           ""],
      KEYWORDS            => [ 6,     "string",   512, 1,           ""],
      CONTACT_NAME        => [ 7,     "string",   128, 1,           ""],
      CONTACT_EMAIL       => [ 8,     "string",   128, 1,           ""],
      PASSWORD            => [ 9,     "string",    64, 1,           ""],
      APPROVED            => [10,     "number",     1, 0,          "0"],
      HITS		            => [11,     "number",     8, 0,	       "0"],
      CREATE_SECOND	      => [12,     "number",     9, 1,           ""],
      CREATE_DATE	        => [13,     "string",    64, 1,           ""],
      CREATE_TIME	        => [14,     "string",    64, 1,           ""],
      UPDATE_SECOND	      => [15,     "number",     9, 0,           ""],
      UPDATE_DATE	        => [16,     "string",    64, 0,           ""],
      UPDATE_TIME	        => [17,     "string",    64, 0,           ""]
    };
    $DB::PrimaryKey{$Table} = "ID";
  } elsif ($Table eq "Categories") {
    $DB::Table{$Table} = {
      ID		              => [ 0, "autonumber",    16, 1,           ""],
      NAME                => [ 1,     "string",    64, 1,           ""],
      DESCRIPTION         => [ 2,     "string",   256, 0,           ""],
      FULL_PATH	          => [ 3,     "string", 32768, 1,           ""],
      ID_PATH	            => [ 4,     "string", 32768, 0,	        ""],
      ROOT		            => [ 5,     "number",    16, 0,	       "0"],
      RESOURCES           => [ 6,     "number",    16, 0,          "0"]
    };
    $DB::PrimaryKey{$Table} = "ID";
  } elsif ($Table eq "Rates") {
    $DB::Table{$Table} = {
      ID		              => [ 0, "autonumber",    16, 1,           ""],
      LID		              => [ 1,     "number",    16, 1,           ""],
      RATE		            => [ 2,     "number",     2, 1,           ""],
      IP		              => [ 3,     "string",    15, 0,           ""]
    };
    $DB::PrimaryKey{$Table} = "ID";
  } elsif ($Table eq "Reviews") {
    $DB::Table{$Table} = {
      ID		              => [ 0, "autonumber",    16, 1,           ""],
      LID		              => [ 1,     "number",    16, 1,           ""],
      REVIEW	            => [ 2,       "text",    -1, 1,           ""],
      IP		              => [ 3,     "string",    15, 0,           ""]
    };
    $DB::PrimaryKey{$Table} = "ID";
  } elsif ($Table eq "Admins") {
    $DB::Table{$Table} = {
      USERNAME	          => [ 0,     "string",    48, 1,           ""],
      PASSWORD	          => [ 1,     "string",    64, 1,           ""],
      NAME		            => [ 2,     "string",   128, 1,           ""]
    };
    $DB::PrimaryKey{$Table} = "USERNAME";
  }

  $DB::Pointer{$Table} = "";

  foreach my $Key ( sort { $DB::Table{$Table}->{$a}[0] <=> $DB::Table{$Table}->{$b}[0] } keys %{ $DB::Table{$Table} } ) {
    push ( @{ $DB::Columns{$Table} }, $Key );
    if ($Key eq $DB::PrimaryKey{$Table}) {
      $DB::Primary{$Table} = $DB::Table{$Table}->{$Key}[0];
    }
    if ($DB::Table{$Table}->{$Key}[1] eq "text") {
      $DB::TextArea{$Table}->{$Key} = $#{$DB::Columns{$Table}};
    }
  }

  return 1;
}

###############################################################################
# myClearCfg subroutine
sub myClearCfg {
  my $self = shift;
  my ($Table) = @_;

  $DB::Path{$Table} = "";
  $DB::Table{$Table} = {};
  $DB::PrimaryKey{$Table} = "";
  $DB::Pointer{$Table} = "";
  $DB::Columns{$Table} = [];
  $DB::Primary{$Table} = "";
  $DB::TextArea{$Table} = {};

  return 1;
}

1;